A Cloud Development Environment(CDE) is a type of software development workspace that runs in the cloud, rather than on your local computer. It allows you to write, run, and debug code entirely through a web browser from any device.

Background

Recently, my MacBook Pro has started having issues that sometimes prevent me from working. It's already eight years old and due for an upgrade. However, I'm waiting for Apple's Back to School promotion, which starts in June, before buying a new one. In the meantime, I need an alternative solution for the next few months. I do have an iPad with a keyboard, so I'm exploring ways to use it for coding.

Solutions

Initially, I set up a Linux system on the cloud and connected to it remotely using my iPad. However, I found the experience laggy and dependent on a high-speed internet connection. The pointer movement wasn’t smooth either, which made it difficult to work efficiently. Because of that, I switched to my current solution: using Code Server. It runs entirely in the browser, and with PWA support, it can even be installed as a standalone app on the iPad.

In addition to the IDE, I also need access to other essential development tools—such as database management, API testing, and version control—that work well in a browser environment on the iPad.

ItemApp or ToolsNote
IDECode ServerCode on browsers like VS Code, most of the extensions are supported
Database ManagerpgAdminManage PostgreSQL database on browser
SSHTermiusVisit and manage VMs via iPad
API testingPosmanPostman browser version, not need to install the app

Set up the CDE using Docker

I decided to use Docker to manage all the development tools because it's easy to maintain, replicate, and deploy across different environments.

1. Install docker and docker-compose

sudo apt update
sudo apt install docker.io docker-compose
sudo systemctl enable docker
sudo systemctl start docker

2. Install Code Server

version: '3'
services:
	code-server:
		build: .
		container_name: code-server
		restart: always
	    volumes:
	      - /root/project:/home/coder/project
	      - /root/docker/code-server/local:/home/coder/.local
	      - /root/.ssh:/home/coder/.ssh
	      - /root/.gitconfig:/home/coder/.gitconfig
	    environment:
	      - PASSWORD=[Your Password]
	      - GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=no
	    expose:
	      - 8080

Besides the project folder, I also created a local folder to persist Code Server settings. This ensures that I don't lose my configuration when restarting the Docker container. I've also configured Code Server to use the same Git and SSH settings as my VM instance, so the development environment stays consistent.

To optimize Code Server for development, I built a custom Dockerfile that installs Node.js, Zsh, and other essential tools, so everything I need is pre-configured and ready to use.

Dockerfile:

FROM ghcr.io/coder/code-server:latest

USER root

# Install Node.js
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
    && apt-get install -y nodejs \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Specify the zsh as default command
RUN chsh -s /usr/bin/zsh coder

USER coder

# Install Oh-My-Ssh
RUN RUNZSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# Create the .zshrc file
RUN echo 'export ZSH="$HOME/.oh-my-zsh"\n\
ZSH_THEME="robbyrussell"\n\
plugins=(\n\
  z\n\
)\n\
source $ZSH/oh-my-zsh.sh\n\
\n\
alias zshconfig="vim ~/.zshrc"\n\
alias gs="git status"\n\
alias gc="git checkout"\n\
alias gcm="git add -A; git commit -am"\n\
alias gpusho="git push origin"\n\
alias gpullo="git pull origin"\n\
' > /home/coder/.zshrc

WORKDIR /home/coder/project

3. Install PostgreSQL and pgAdmin

version: '3'
services:
	postgres:
    image: postgres
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_DB=[Your DB]
      - POSTGRES_PASSWORD=[Your password]
    volumes:
      - postgres_data:/var/lib/postgresql/data

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    restart: always
    environment:
      - PGADMIN_DEFAULT_EMAIL=[Your Email]
      - PGADMIN_DEFAULT_PASSWORD=[Your Password]
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    expose:
      - 80
    depends_on:
      - postgres
volumes:
	postgres_data:
	pgadmin_data:

4. Install Caddy

Caddy is a simpler alternative to Nginx for setting up a reverse proxy. However, if you don’t have a domain or don’t need to access the server via a domain name, you can skip this step and visit the server using the public IP directly.

version: '3'
services:
	caddy:
	    image: caddy:2-alpine
	    container_name: caddy
	    restart: always
	    ports:
	      - "80:80"
	      - "443:443"
	    volumes:
	      - ./Caddyfile:/etc/caddy/Caddyfile
	      - caddy_data:/data
	      - caddy_config:/config
	    depends_on:
	      - code-server
	      - pgadmin
volumes:
	caddy_data:
	caddy_config:

Next, I created a Caddyfile to configure the reverse proxy. This allows us to use a subdomain for accessing the different services.

[Your Domain 1] {
    reverse_proxy code-server:8080
}

[Your Domain 2] {
    reverse_proxy pgadmin:80
}

Here is the whole docker-compose.yml configuration.

services:
  code-server:
    build: .
    container_name: code-server
    restart: always
    volumes:
      - /root/project:/home/coder/project
      - /root/docker/code-server/local:/home/coder/.local
      - /root/.ssh:/home/coder/.ssh
      - /root/.gitconfig:/home/coder/.gitconfig
    environment:
      - PASSWORD=[Your Password]
      - GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=no
    expose:
      - 8080

  postgres:
    image: postgres
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_DB=[Your DB nae]
      - POSTGRES_PASSWORD=[Your Password]
    volumes:
      - postgres_data:/var/lib/postgresql/data

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    restart: always
    environment:
      - PGADMIN_DEFAULT_EMAIL=[Your Email]
      - PGADMIN_DEFAULT_PASSWORD=[Your Password]
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    expose:
      - 80
    depends_on:
      - postgres

  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - code-server
      - pgadmin

volumes:
  caddy_data:
  caddy_config:
  postgres_data:
  pgadmin_data:

Once the Docker services have been started, you can visit the Code Server and pgAdmin via browsers.

Any server running inside Code Server can be accessed through a proxy path in the format: {code-server-domain}/proxy/{port}. For example, if a server previously ran locally on port 5000, instead of visiting it at localhost:5000, you would now access it via https://your-code-server-domain/proxy/5000.

You won't be able to access any server running inside Code Server without being logged in first, so make sure to sign in before testing. If you're using Postman to test an API, you'll also need to include the code-server-session cookie in your requests. Fortunately, Postman offers a browser extension called Postman Interceptor, which makes it easy to sync cookies directly from your browser to Postman.

Summary

After using Code Server for several months, I'm quite satisfied with it. It is almost identical to running VS Code locally, and in some cases, it's even more efficient, especially when working with APIs that require an HTTPS address, which localhost doesn't provide.

However, it's not well-suited for mobile Application development. You can't simulate a phone on this platform, and if you're developing iOS apps, the experience is even more limited since Xcode is required and required and only runs on macOS. Additionally, if the project includes a large number of binary files, such as images, it won't be shown immediately because it's a browser-based environment and needs time to load.